package com.lang.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.lang.DownloadConfig;
import com.lang.JsonConfig.JSONConfig;
import com.lang.exception.DownloadconfigManagerException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

/**
 * 下载任务的配置信息 管理器
 */
@Slf4j
public class DownloadConfigManager {

    /**
     * 每个配置文件的阻塞队列
     */
    private final static Map<String, LinkedBlockingDeque<DownloadConfig>> CONFIG_PATH_MAP = new ConcurrentHashMap<>();
    /**
     * 已经完成的配置文件
     */
    private final static Set<String> FINISH_FILE = new CopyOnWriteArraySet<>();

    static {
        JSONConfig.init();
        Timer WRITE_CONFIG_MANAGER_TIMER = new Timer("管理-写入配置文件-定时器", true);
        WRITE_CONFIG_MANAGER_TIMER.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                if (!CONFIG_PATH_MAP.isEmpty()) {
                    for (Map.Entry<String, LinkedBlockingDeque<DownloadConfig>> entry : CONFIG_PATH_MAP.entrySet()) {
                        consumeConfigDataInQueue(entry);
                    }
                }
            }
        }, 0, TimeUnit.SECONDS.toMillis(1));
        log.info("管理-写入配置文件-定时器 启动");
    }

    /**
     * 放入等待写入配置文件队列
     *
     * @param configFilePath 配置文件路径
     * @param downloadConfig 配置信息
     */
    private static void putConfigDataInQueue(String configFilePath, DownloadConfig downloadConfig) {
        CONFIG_PATH_MAP.computeIfAbsent(configFilePath, s -> new LinkedBlockingDeque<>(50));
        LinkedBlockingDeque<DownloadConfig> downloadConfigQueue = CONFIG_PATH_MAP.get(configFilePath);
        try {
            if (downloadConfigQueue.size() == 50)
                downloadConfigQueue.clear();
            downloadConfigQueue.offerLast(downloadConfig, 1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("放入等待写入配置文件队列=" + configFilePath, e);
        }
    }

    /**
     * 即时直接写入配置文件信息
     *
     * @param configFilePath 配置文件路径
     * @param downloadConfig 配置信息
     */
    private static void writeConfigDataInFile(String configFilePath, DownloadConfig downloadConfig) {
        try {
            Path configPath = Paths.get(configFilePath); //配置文件
            Path configTempPath = Paths.get(tempConfigFile(configFilePath)); //配置文件临时文件
            if (Files.exists(configTempPath)) {
                if (!Files.isDirectory(configTempPath)) {
                    Files.delete(configTempPath);
                }
            }
            Files.createFile(configTempPath);
            OutputStream outputStream = Files.newOutputStream(configTempPath);
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
            BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
            bufferedWriter.write(JSON.toJSONString(downloadConfig, SerializerFeature.PrettyFormat, SerializerFeature.WriteClassName));
            bufferedWriter.flush();
            bufferedWriter.close();
            outputStreamWriter.close();
            outputStream.close();
            if (Files.exists(configPath)) {
                if (!Files.isDirectory(configPath)) {
                    Files.delete(configPath);
                }
            }
            Files.move(configTempPath, configPath);
        } catch (IOException e) {
            log.error("即时直接写入配置文件信息失败=" + configFilePath, e);
        }
    }

    /**
     * 消费阻塞队列中的配置信息
     *
     * @param entry 配置信息队列
     */
    private static void consumeConfigDataInQueue(Map.Entry<String, LinkedBlockingDeque<DownloadConfig>> entry) {
        String configFileSavePath = entry.getKey();
        LinkedBlockingDeque<DownloadConfig> configBlockingQueue = entry.getValue();
        if (FINISH_FILE.contains(configFileSavePath))
            return;
        if (configBlockingQueue.isEmpty())
            return;
        DownloadConfig downloadConfig = null;
        try {
            downloadConfig = configBlockingQueue.pollLast(1, TimeUnit.MILLISECONDS);
            configBlockingQueue.clear();
        } catch (InterruptedException e) {
            log.error("获取队列中的配置信息失败=" + configFileSavePath, e);
        }
        if (downloadConfig == null)
            return;
        writeConfigDataInFile(configFileSavePath, downloadConfig);
    }

    /**
     * 读取配置文件信息
     *
     * @param configFilePath 配置文件路径
     * @param typeReference  配置信息类型
     * @param <T>            配置信息Java类型
     * @return 配置文件信息
     */
    public static <T> T readConfig(String configFilePath, Class<T> typeReference) {
        Path configPath = Paths.get(configFilePath); //配置文件
        Path configTempPath = Paths.get(tempConfigFile(configFilePath)); //配置文件临时文件
        if (Files.notExists(configPath) && Files.notExists(configTempPath))
            throw new DownloadconfigManagerException("配置文件不存在" + configFilePath);
        if (Files.isDirectory(configPath) && Files.isDirectory(configTempPath))
            throw new DownloadconfigManagerException("指定路径不是可读文件" + configFilePath);
        List<String> lines;
        try {
            InputStream inputStream;
            if (Files.exists(configPath))
                inputStream = Files.newInputStream(configPath);
            else
                inputStream = Files.newInputStream(configTempPath);
            lines = IOUtils.readLines(inputStream, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new DownloadconfigManagerException("读取文件流异常" + configFilePath, e);
        }
        if (lines.isEmpty()) {
            throw new DownloadconfigManagerException("配置文件无有效信息" + configFilePath);
        }
        String line = String.join("", lines);
        return JSON.parseObject(line, typeReference, Feature.SupportAutoType);
    }

    /**
     * 写配置信息文件
     *
     * @param configFileSavePath 配置文件路径
     * @param config             配置信息
     * @param <T>                配置信息类型
     */
    public static <T extends DownloadConfig> void writeDownloadConfig(String configFileSavePath, T config) {
        if (config.isDone()) {
            // 已经是最后一条信息 直接写入配置信息
            if (CONFIG_PATH_MAP.containsKey(configFileSavePath))
                CONFIG_PATH_MAP.get(configFileSavePath).clear();
            FINISH_FILE.add(configFileSavePath);
            writeConfigDataInFile(configFileSavePath, config);
        } else {
            // 放入等待队列
            putConfigDataInQueue(configFileSavePath, config);
        }
    }

    /**
     * 定义临时配置文件路径
     *
     * @param configFilePath 配置文件路径
     * @return 临时配置文件路径
     */
    public static String tempConfigFile(String configFilePath) {
        return configFilePath + ".temp";
    }
}
